#include "./ota.h"

#include "../cfg/ota_info.h"
#include "../driver/cmd_uart.h"
#include "../utils/log.h"
#include "../device/compat.h"


namespace service::ota {

using namespace driver;
using namespace cfg;
using namespace utils;

struct OtaCtx {
    uint32_t pkg_size = 0;
    uint16_t pkg_id = 0;
    uint32_t pkg_index = 0;

    void init(uint32_t pkg_size) {
        this->pkg_size = pkg_size;
        pkg_id = 0;
        pkg_index = 0;
    }

    Error write(uint16_t id, const ByteView &bw) {
        Error err;
        if (id != pkg_id) {
            err = ec::OTA_ID_INVALID;
            goto finish;
        }

        flash::write(flash::OTA_DATA_ADDR + pkg_index, bw.buf, bw.len);
        pkg_index += bw.len;
        pkg_id++;

    finish:
        return err;
    }
};

static OtaCtx otaCtx;

void on_start(const conn::Message &msg) {
    uint32_t pkg_size;
    Error err = msg.parse_req(pkg_size);
    if (err) {
        goto finish;
    }
    flash::erase(flash::OTA_DATA_ADDR, pkg_size);
    if (err) {
        goto finish;
    }
    otaCtx.init(pkg_size);
finish:
    cmdUart.simple_res(msg, err);
}

void on_translate(const conn::Message &msg) {
    uint16_t id;
    ByteView bw;
    Error err = msg.parse_req(id, bw);
    if (err) {
        goto finish;
    }
    err = otaCtx.write(id, bw);
finish:
    cmdUart.simple_res(msg, err);
}

void on_complete(const conn::Message &msg) {
    ByteView md5_bw;
    Error err = msg.parse_req(md5_bw);
    if (err) {
        goto finish;
    }

    if (md5_bw.len != 16) {
        err = ec::OTA_MD5_INVALID;
        goto finish;
    }

    if (otaCtx.pkg_index != otaCtx.pkg_size) {
        err = ec::OTA_PKG_INVALID;
        goto finish;
    }

    uint8_t md5[16];
    utils::md5::sum(reinterpret_cast<const void *>(flash::OTA_DATA_ADDR),
                    otaCtx.pkg_size, md5);

    if (memcmp(md5, md5_bw.buf, 16) != 0) {
        err = ec::OTA_MD5_INVALID;
        goto finish;
    }

    otaInfo->init(otaCtx.pkg_size);
    err = otaInfo.save();

finish:
    cmdUart.simple_res(msg, err);

    if (err.is_ok()) {
        osDelay(500);
        NVIC_SystemReset();
    }
}

} // namespace service::ota
